<script setup lang="tsx">
import { computed, nextTick, reactive, ref, watch } from 'vue';
import { NButton } from 'naive-ui';
import { useFormRules, useNaiveForm } from '@/hooks/common/form';
import { $t } from '@/locales';
import type { Filters, GatewayRoute, GatewayRouteEditAndCreateParams, Predicates, PredicatesArgs } from '@/service/api';
import { createGatewayRoute, updateGatewayRoute } from '@/service/api';
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict';

defineOptions({
  name: 'GatewayRouteOperateModel'
});

interface Props {
  /** the type of operation */
  operateType: NaiveUI.TableOperateType;
  /** the edit row data */
  rowData?: GatewayRoute | null;
}

const props = defineProps<Props>();

interface Emits {
  (e: 'submitted'): void;
}

const emit = defineEmits<Emits>();

const visible = defineModel<boolean>('visible', {
  default: false
});

const { formRef, validate, restoreValidation } = useNaiveForm();
const { defaultRequiredRule } = useFormRules();

const submitLoading = ref(false);

const title = computed(() => {
  const titles: Record<NaiveUI.TableOperateType, string> = {
    add: $t('page.base.route.addGatewayRoute'),
    edit: $t('page.base.route.editGatewayRoute')
  };
  return titles[props.operateType];
});

type Model = GatewayRouteEditAndCreateParams;

const model: Model = reactive(createDefaultModel());

function createDefaultModel(): Model {
  return {
    id: 0,
    routerId: '',
    name: '',
    uri: '',
    predicates: [],
    filters: [],
    retryable: null,
    stripPrefix: '',
    persistable: '',
    showApi: '',
    status: 1
  };
}

const inputRef = ref();
const inputRef2 = ref();
const state = reactive({
  inputVisible: false,
  inputValue: ''
});
const currentNameIndex = ref(0);
const currentTagIndex = ref(-1);

const noKeyRouter = ['Path', 'Host', 'Method', 'After', 'Before', 'Between', 'RemoteAddr'];
const tagArray = ref<Predicates[]>([
  {
    name: 'Path',
    args: []
  },
  {
    name: 'Header',
    args: {
      header: '',
      regexp: ''
    }
  },
  {
    name: 'Query',
    args: {
      param: '',
      regexp: ''
    }
  },
  {
    name: 'Method',
    args: []
  },
  {
    name: 'Host',
    args: []
  },
  {
    name: 'Cookie',
    args: {
      name: '',
      regexp: ''
    }
  },
  {
    name: 'After',
    args: []
  },
  {
    name: 'Before',
    args: []
  },
  {
    name: 'Between',
    args: []
  },
  {
    name: 'RemoteAddr',
    args: []
  }
]);
const predicateOptions = [
  {
    label: 'Path',
    key: 'Path'
  },
  {
    label: 'Header',
    key: 'Header'
  },
  {
    label: 'Query',
    key: 'Query'
  },
  {
    label: 'Method',
    key: 'Method'
  },
  {
    label: 'Host',
    key: 'Host'
  },
  {
    label: 'Cookie',
    key: 'Cookie'
  },
  {
    label: 'After',
    key: 'After'
  },
  {
    label: 'Before',
    key: 'Before'
  },
  {
    label: 'Between',
    key: 'Between'
  },
  {
    label: 'RemoteAddr',
    key: 'RemoteAddr'
  }
];
const filterOptions = [/* { key: 0, name: '熔断器' }, */ { key: 1, label: '限流过滤器' }];
// 路由条件
const predicates = ref<Predicates[]>([]);
// 过滤器
const filters = ref<Filters[]>([]);

type RuleKey = Extract<keyof Model, 'routerId' | 'uri' | 'name'>;

const rules: Record<RuleKey, App.Global.FormRule> = {
  routerId: defaultRequiredRule,
  name: defaultRequiredRule,
  uri: defaultRequiredRule
};

function handleInitModel() {
  Object.assign(model, createDefaultModel());

  predicates.value = [];
  filters.value = [];

  if (props.operateType === 'edit' && props.rowData) {
    Object.assign(model, props.rowData);
    predicates.value = JSON.parse(props.rowData.predicates);
    filters.value = JSON.parse(props.rowData.filters);
  }
}

function closeDrawer() {
  submitLoading.value = false;
  visible.value = false;
}

watch(visible, () => {
  if (visible.value) {
    handleInitModel();
    restoreValidation();
  }
});

/**
 * 添加路由条件
 *
 * @param key
 */
const predicatesHandleMenuClick = (key: string) => {
  const routeType = tagArray.value.find(item => item.name === key);
  if (routeType) {
    predicates.value.push({
      name: key,
      args: routeType.args
    });
  }
};

/**
 * 删除路由条件
 *
 * @param item
 * @param index
 */
function removePredicate(item: Predicates[], index: number) {
  item.splice(index, 1);
}

/**
 * 显示输入框
 *
 * @param _item
 * @param index
 */
const showInput = (_item: Predicates, index: number) => {
  state.inputValue = '';
  state.inputVisible = true;
  currentNameIndex.value = index;
  nextTick(() => {
    inputRef.value[0].focus();
  });
};

/**
 * 输入框确认
 *
 * @param item
 */
function handlePredicateInputConfirm(item: any) {
  const tags = item.args;
  const inputValue = state.inputValue;
  if (inputValue && !tags.includes(inputValue)) {
    item.args = [...tags, state.inputValue];
  }
  state.inputVisible = false;
  state.inputValue = '';
  currentTagIndex.value = -1;
  currentNameIndex.value = -1;
}

/**
 * 输入框确认
 *
 * @param item
 * @param tag
 * @param index
 */
function handleInputEditConfirm(item: any, _tag: any, index: number) {
  const inputValue = state.inputValue;
  if (inputValue) {
    item.args[index] = state.inputValue;
  }
  currentTagIndex.value = -1;
  currentNameIndex.value = -1;
}

/** 删除路由条件配置项 */
function removePredicateTag(item: Predicates, removedTag: any) {
  const tags = item.args.filter((tag: any) => tag !== removedTag);
  item.args = tags;
}

/**
 * 编辑路由条件配置项
 *
 * @param _item
 * @param tag
 * @param tagIndex
 * @param index
 */
// eslint-disable-next-line max-params
function editPredicateTag(_item: Predicates, tag: any, tagIndex: number, index: number) {
  currentNameIndex.value = index;
  currentTagIndex.value = tagIndex;
  state.inputValue = tag;
  nextTick(() => {
    inputRef2.value[0].focus();
  });
}

/**
 * 值修改事件
 *
 * @param e
 * @param item
 * @param key
 */
function valueChange(val: string, item: any, key: string) {
  item[key] = val;
}

/**
 * 删除过滤器
 *
 * @param item
 * @param index
 */
function removeFilter(item: Filters[], index: number) {
  item.splice(index, 1);
}

/**
 * 删除过滤器参数
 *
 * @param item
 * @param index
 */
function removeFilterParams(item: Filters, index: number) {
  item.args.splice(index, 1);
}

/**
 * 添加过滤器参数
 *
 * @param item
 */
function addFilterParams(item: Filters) {
  item.args.push({
    key: `key${item.args.length}${1}`,
    value: ''
  });
}

/**
 * 过滤器添加事件
 *
 * @param e
 */
function filterHandleMenuClick(key: number) {
  if (key === 0) {
    filters.value.push({
      args: [
        {
          key: 'name',
          value: 'default'
        },
        {
          key: 'fallbackUri',
          value: 'forward:/fallback'
        }
      ],
      name: 'Hystrix',
      title: filterOptions[0].label
    });
  }

  if (key === 1) {
    filters.value.push({
      args: [
        {
          key: 'key-resolver',
          value: '#{@ipKeyResolver}'
        },
        {
          key: 'redis-rate-limiter.replenishRate',
          value: 20
        },
        {
          key: 'redis-rate-limiter.burstCapacity',
          value: 20
        }
      ],
      name: 'RequestRateLimiter',
      title: filterOptions[0].label
    });
  }
}

/** 提交 */
async function handleSubmit() {
  model.predicates = predicates.value;
  model.filters = filters.value;

  await validate();

  submitLoading.value = true;

  if (props.operateType === 'edit') {
    // 重新构造表单提交对象,切记不可修改router对象，数组修改为字符串容易造成界面混乱
    const params = { ...model, predicates: JSON.stringify(model.predicates), filters: JSON.stringify(model.filters) };
    const { error } = await updateGatewayRoute(model.id, params);

    if (!error) {
      window.$message?.success($t('common.updateSuccess'));
      closeDrawer();
      emit('submitted');
    }
  } else {
    // 重新构造表单提交对象,切记不可修改router对象，数组修改为字符串容易造成界面混乱
    const params = { ...model, predicates: JSON.stringify(model.predicates), filters: JSON.stringify(model.filters) };
    const { error } = await createGatewayRoute(params);
    if (!error) {
      window.$message?.success($t('common.addSuccess'));
      closeDrawer();
      emit('submitted');
    }
  }
}
</script>

<template>
  <NModal v-model:show="visible" :title="title" preset="card" class="w-200">
    <NScrollbar class="max-h-200 pr-20px">
      <NForm ref="formRef" :model="model" :rules="rules" label-placement="left" label-width="auto">
        <NFormItem :label="$t('page.base.route.routerId')" path="routerId">
          <NInput v-model:value="model.routerId" disabled :placeholder="$t('page.base.route.form.routerId')" />
        </NFormItem>
        <NFormItem :label="$t('page.base.route.name')" path="name">
          <NInput v-model:value="model.name" :placeholder="$t('page.base.route.form.name')" />
        </NFormItem>
        <NFormItem :label="$t('page.base.route.uri')" path="uri">
          <NInput v-model:value="model.uri" :placeholder="$t('page.base.route.form.uri')" />
        </NFormItem>
        <NFormItem :label="$t('page.base.route.status')" path="status">
          <NRadioGroup v-model:value="model.status">
            <NRadio
              v-for="item in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
              :key="item.value"
              :value="item.value"
              :label="item.label"
            />
          </NRadioGroup>
        </NFormItem>
        <!-- 路由条件 -->
        <NFormItem :label="$t('page.base.route.predicates')" path="predicates">
          <NGrid cols="1" x-gap="8" :y-gap="12">
            <NGi>
              <div v-for="(item, index) in predicates" :key="item.name">
                <NDivider dashed title-placement="center">
                  {{ item.name }} &nbsp;
                  <icon-hugeicons:delete-01 class="cursor-pointer" @click="removePredicate(predicates, index)" />
                </NDivider>
                <div v-if="noKeyRouter.includes(item.name)">
                  <!-- 显示 -->
                  <div v-for="(tag, tagIndex) in item.args as []" :key="tagIndex" class="mr-8px inline">
                    <NInput
                      v-if="tagIndex == currentTagIndex && index == currentNameIndex"
                      ref="inputRef2"
                      v-model:value="state.inputValue"
                      size="small"
                      class="pl-4 pr-4"
                      autosize
                      @blur="handleInputEditConfirm(item, tag, tagIndex)"
                      @keyup.enter="handleInputEditConfirm(item, tag, tagIndex)"
                    />
                    <NTag
                      v-else
                      closable
                      class="pl-4 pr-4"
                      @close="removePredicateTag(item, tag)"
                      @click="editPredicateTag(item, tag, tagIndex, index)"
                    >
                      {{ tag }}
                    </NTag>
                  </div>
                  <!-- 添加 -->
                  <div class="float-start mr-8px">
                    <NInput
                      v-if="state.inputVisible && index == currentNameIndex"
                      ref="inputRef"
                      v-model:value="state.inputValue"
                      class="min-w-30 pl-4 pr-4"
                      autosize
                      size="small"
                      @blur="handlePredicateInputConfirm(item)"
                      @keyup.enter="handlePredicateInputConfirm(item)"
                    />
                    <NButton v-else dashed size="small" class="pl-4 pr-4" @click="showInput(item, index)">
                      <template #icon>
                        <icon-ep:plus />
                      </template>
                      {{ $t('common.new') }} {{ item.name }}
                    </NButton>
                  </div>
                </div>
                <div v-if="!noKeyRouter.includes(item.name)">
                  <div v-for="(value, key) in item.args as PredicatesArgs []" :key="key" class="mr-8px inline">
                    <NRow>
                      <NCol :span="5">
                        <span v-if="key.toString() === 'header'">{{ $t('page.base.route.headerName') }}</span>
                        <span v-if="key.toString() === 'regexp'">{{ $t('page.base.route.paramValue') }}</span>
                        <span v-if="key.toString() === 'param'">{{ $t('page.base.route.paramName') }}</span>
                        <span v-if="key.toString() === 'name'">{{ $t('page.base.route.paramName') }}</span>
                      </NCol>
                      <NCol :span="18">
                        <NInput
                          :default-value="value.value"
                          :placeholder="key.toString()"
                          @change="e => valueChange(e, item.args, key.toString())"
                        />
                      </NCol>
                    </NRow>
                  </div>
                </div>
              </div>
            </NGi>
            <NGi>
              <NDropdown class="w-150" trigger="hover" :options="predicateOptions" @select="predicatesHandleMenuClick">
                <NButton dashed icon-placement="right" class="w-100%">
                  {{ $t('page.base.route.addPredicates') }}
                  <template #icon>
                    <icon-ep:arrow-down />
                  </template>
                </NButton>
              </NDropdown>
            </NGi>
          </NGrid>
        </NFormItem>

        <!-- 添加过滤器 -->
        <NFormItem :label="$t('page.base.route.filters')" path="filters">
          <NGrid cols="1" x-gap="8" :y-gap="12">
            <NGi>
              <div v-for="(item, index) in filters" :key="index">
                <NDivider dashed title-placement="center">
                  {{ item.name }} &nbsp;
                  <icon-hugeicons:delete-01 class="cursor-pointer" @click="removeFilter(filters, index)" />
                </NDivider>
                <div>
                  <div v-for="(tag, tagIndex) in item.args" :key="tag.key">
                    <NInput v-model:value="tag.key" placeholder="参数键" class="fileters-input-1" />
                    <NInput v-model:value="tag.value" placeholder="参数值" class="fileters-input-2 mt-1" />
                    <icon-ep:close class="cursor-pointer" @click="removeFilterParams(item, tagIndex)" />
                  </div>
                  <div>
                    <NButton dashed size="small" class="mt-2 w-100%" @click="addFilterParams(item)">
                      <template #icon>
                        <icon-ep:plus />
                      </template>
                      {{ $t('page.base.route.addParams') }}
                    </NButton>
                  </div>
                </div>
              </div>
            </NGi>
            <NGi>
              <NDropdown class="w-150" trigger="hover" :options="filterOptions" @select="filterHandleMenuClick">
                <NButton dashed icon-placement="right" class="w-100%">
                  {{ $t('page.base.route.addFilters') }}
                  <template #icon>
                    <icon-ep:arrow-down />
                  </template>
                </NButton>
              </NDropdown>
            </NGi>
          </NGrid>
        </NFormItem>
      </NForm>
    </NScrollbar>
    <template #footer>
      <NSpace justify="end" :size="16">
        <NButton @click="closeDrawer">{{ $t('common.cancel') }}</NButton>
        <NButton type="primary" :loading="submitLoading" @click="handleSubmit">{{ $t('common.confirm') }}</NButton>
      </NSpace>
    </template>
  </NModal>
</template>

<style scoped>
.fileters-input-1 {
  width: 45%;
  margin-right: 8px;
}
.fileters-input-2 {
  width: 45%;
  margin-right: 8px;
}
</style>
